﻿#include "pch.h"
#include "Game.h"

/**
 * 构造方法。
 * @param callback  回调
 * @param drawer    绘图者
 * @param gameBoard 游戏区域
 */
Game::Game(CWnd* callback, CDC* drawer, CWnd* gameBoard) :
    mCallback(callback),
    mDrawer(drawer),
    mGameBoard(gameBoard), 
    mStatus(END) {
    // 初始化成员
    mFont.CreatePointFont(Const::GAME_BOARD_FONT_SIZE, Const::GAME_BOARD_FONT_NAME, mDrawer);
    mDrawer->SelectObject(mFont);
    mDrawer->SetBkMode(TRANSPARENT);

    // 重绘游戏区域
    this->DrawGameBoard();

    // 置随机数种子
    srand((UINT)time(NULL));
}

/**
 * 移动之后要做的事。
 * @param changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 TRUE，否则为 FALSE
 */
void Game::AfterMove(BOOL changed) {
    // 重绘游戏区域
    this->DrawGameBoard();

    // 画所有数字
    this->DrawAllNumbers();

    // 如果胜利或失败，则游戏结束
    if (this->IsWon()) {
        this->GameOver(TRUE);
        return;
    }
    if (this->IsLost()) {
        this->GameOver(FALSE);
        return;
    }

    // 创建下一个数
    if (changed) {
        this->CreateRandomNumber();
    }
}

/**
 * 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
 */
void Game::CreateRandomNumber() {
    int row = rand() % Const::GAME_BOARD_GRID_COUNT_V;
    int line = rand() % Const::GAME_BOARD_GRID_COUNT_H;

    while (mNumbers[row][line] != 0) {
        row = rand() % Const::GAME_BOARD_GRID_COUNT_V;
        line = rand() % Const::GAME_BOARD_GRID_COUNT_H;
    }

    mNumbers[row][line] = (rand() % 8) <= 3 ? 2: 4;
    this->DrawOneNumber(line, row, mNumbers[row][line]);
    mGameBoard->Invalidate(FALSE);
}

/**
 * 画所有数字。
 */
void Game::DrawAllNumbers() {
    for (int i = 0; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
        for (int j = 0; j < Const::GAME_BOARD_GRID_COUNT_H; j++) {
            if (mNumbers[i][j] != 0) {
                this->DrawOneNumber(j, i, mNumbers[i][j]);
            }
        }
    }

    mGameBoard->Invalidate(FALSE);
}

/**
 * 画游戏区域。
 */
void Game::DrawGameBoard() {
    // 画边框
    mDrawer->Rectangle(0, 0, Const::GAME_BOARD_WIDTH - 1, Const::GAME_BOARD_HEIGHT - 1);

    // 画背景
    mDrawer->FillSolidRect(1, 1, Const::GAME_BOARD_WIDTH - 2, Const::GAME_BOARD_HEIGHT - 2, Const::GAME_BOARD_COLOR);

    // 画竖线
    int i;
    for (i = 1; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
        mDrawer->MoveTo(Const::GRID_SIZE * i, 0);
        mDrawer->LineTo(Const::GRID_SIZE * i, Const::GAME_BOARD_HEIGHT);
    }

    // 画横线
    for (i = 1; i < Const::GAME_BOARD_GRID_COUNT_H; i++) {
        mDrawer->MoveTo(0, Const::GRID_SIZE * i);
        mDrawer->LineTo(Const::GAME_BOARD_WIDTH, Const::GRID_SIZE * i);
    }

    // 通知游戏区域重绘
    mGameBoard->Invalidate(FALSE);
}

/**
 * 画单个数字。
 * @param line      列
 * @param row       行
 * @param number    要画的数字
 */
void Game::DrawOneNumber(int line, int row, int number) {
    // 计算位置
    RECT rect = { 0 };
    rect.left = line * Const::GRID_SIZE;
    rect.top = row * Const::GRID_SIZE;
    rect.right = rect.left + Const::GRID_SIZE;
    rect.bottom = rect.top + Const::GRID_SIZE;
    
    // 画数字
    CString text;
    text.Format(_T("%d"), number);
    mDrawer->DrawText(text, text.GetLength(), &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

/**
 * 游戏结束。
 * @param isWon 胜利则为 TRUE，否则为 FALSE
 */
void Game::GameOver(BOOL isWon) {
    // 将状态设置为已结束
    mStatus = END;

    // 发送通知
    mCallback->SendMessage(Const::UM_GAME_OVER, isWon, 0);
}

/**
 * 判断是否失败。
 * @return 失败则返回 TRUE，否则返回 FALSE
 */
BOOL Game::IsLost() {
    int i, j;   // 循环变量

    // 横向检查所有数字
    for (i = 0; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
        for (j = 0; j < Const::GAME_BOARD_GRID_COUNT_H - 1; j++) {
            if (mNumbers[i][j] == 0 || mNumbers[i][j] == mNumbers[i][j + 1]) {
                return FALSE;
            }
        }
    }

    // 纵向检查所有数字
    for (j = 0; j < Const::GAME_BOARD_GRID_COUNT_H; j++) {
        for (i = 0; i < Const::GAME_BOARD_GRID_COUNT_V - 1; i++) {
            if (mNumbers[i][j] == mNumbers[i + 1][j]) {
                return FALSE;
            }
        }
    }

    // 判断最右边一列是否有零
    for (i = 0; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
        if (mNumbers[i][3] == 0) {
            return FALSE;
        }
    }

    return TRUE;
}

/**
 * 判断是否胜利。
 * @return 胜利则返回 TRUE，否则返回 FALSE
 */
BOOL Game::IsWon() {
    // 检查所有数字
    for (int i = 0; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
        for (int j = 0; j < Const::GAME_BOARD_GRID_COUNT_H; j++) {
            if (mNumbers[i][j] == Const::WIN_NUMBER) {
                return TRUE;
            }
        }
    }

    return FALSE;
}

/**
 * 下移。
 */
void Game::MoveDown() {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从左到右遍历每一列
    for (j = 0; j < Const::GAME_BOARD_GRID_COUNT_H; j++) {
        // 每列从上到下遍历每一格，判断是否有相同数，有则相加
        for (i = 0; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
            // 如果该数为零或之前被加过，则什么也不做
            if (mNumbers[i][j] == 0 || i == ignore) {
                continue;
            }

            // 不为零则从该数下方逐个检查
            for (k = i + 1; k < Const::GAME_BOARD_GRID_COUNT_V; k++) {
                // 遇到零跳过
                if (mNumbers[k][j] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (mNumbers[k][j] != mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                mNumbers[k][j] += mNumbers[i][j];
                mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向下移动
        for (i = Const::GAME_BOARD_GRID_COUNT_V - 2; i >= 0; i--) {
            // 如果该数为零（没有数），则什么也不做
            if (mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向下移动
            for (k = i + 1; k < Const::GAME_BOARD_GRID_COUNT_V; k++) {
                if (mNumbers[k][j] == 0) {
                    mNumbers[k][j] = mNumbers[k - 1][j];
                    mNumbers[k - 1][j] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    this->AfterMove(changed);
}

/**
 * 左移。
 */
void Game::MoveLeft() {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从上到下遍历每一行
    for (i = 0; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
        // 每行从右到左遍历每一格，判断是否有相同数，有则相加
        for (j = Const::GAME_BOARD_GRID_COUNT_H - 1; j > 0; j--) {
            // 如果该数为零或之前被加过，则什么也不做
            if (mNumbers[i][j] == 0 || j == ignore) {
                continue;
            }

            // 不为零则从该数左面逐个检查
            for (k = j - 1; k >= 0; k--) {
                // 遇到零跳过
                if (mNumbers[i][k] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (mNumbers[i][k] != mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                mNumbers[i][k] += mNumbers[i][j];
                mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向左移动
        for (j = 1; j < Const::GAME_BOARD_GRID_COUNT_H; j++) {
            // 如果该数为零（没有数），则什么也不做
            if (mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向左移动
            for (k = j - 1; k >= 0; k--) {
                if (mNumbers[i][k] == 0) {
                    mNumbers[i][k] = mNumbers[i][k + 1];
                    mNumbers[i][k + 1] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    this->AfterMove(changed);
}

/**
 * 右移。
 */
void Game::MoveRight() {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从上到下遍历每一行
    for (i = 0; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
        // 每行从左到右遍历每一格，判断是否有相同数，有则相加
        for (j = 0; j < Const::GAME_BOARD_GRID_COUNT_H - 1; j++) {
            // 如果该数为零或之前被加过，则什么也不做
            if (mNumbers[i][j] == 0 || j == ignore) {
                continue;
            }

            // 不为零则从该数右面逐个检查
            for (k = j + 1; k < Const::GAME_BOARD_GRID_COUNT_H; k++) {
                // 遇到零跳过
                if (mNumbers[i][k] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (mNumbers[i][k] != mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                mNumbers[i][k] += mNumbers[i][j];
                mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向右移动
        for (j = Const::GAME_BOARD_GRID_COUNT_H - 2; j >= 0; j--) {
            // 如果该数为零（没有数），则什么也不做
            if (mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向右移动
            for (k = j + 1; k < Const::GAME_BOARD_GRID_COUNT_H; k++) {
                if (mNumbers[i][k] == 0) {
                    mNumbers[i][k] = mNumbers[i][k - 1];
                    mNumbers[i][k - 1] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    this->AfterMove(changed);
}

/**
 * 上移。
 */
void Game::MoveUp() {
    int i, j, k;            // 循环变量
    int ignore = -1;        // 忽略的格
    BOOL changed = FALSE;   // 是否相加、移动

    // 从左到右遍历每一列
    for (j = 0; j < Const::GAME_BOARD_GRID_COUNT_H; j++) {
        // 每列从下到上遍历每一格，判断是否有相同数，有则相加
        for (i = Const::GAME_BOARD_GRID_COUNT_V - 1; i > 0; i--) {
            // 如果该数为零或之前被加过，则什么也不做
            if (mNumbers[i][j] == 0 || i == ignore) {
                continue;
            }

            // 不为零则从该数上方逐个检查
            for (k = i - 1; k >= 0; k--) {
                // 遇到零跳过
                if (mNumbers[k][j] == 0) {
                    continue;
                }

                // 如果两数不相等，则结束本次循环
                if (mNumbers[k][j] != mNumbers[i][j]) {
                    break;
                }

                // 两数相等则相加
                mNumbers[k][j] += mNumbers[i][j];
                mNumbers[i][j] = 0;
                ignore = k;
                changed = TRUE;
            }
        }

        // 所有数字向上移动
        for (i = 1; i < Const::GAME_BOARD_GRID_COUNT_V; i++) {
            // 如果该数为零（没有数），则什么也不做
            if (mNumbers[i][j] == 0) {
                continue;
            }

            // 不为零则向上移动
            for (k = i - 1; k >= 0; k--) {
                if (mNumbers[k][j] == 0) {
                    mNumbers[k][j] = mNumbers[k + 1][j];
                    mNumbers[k + 1][j] = 0;
                    changed = TRUE;
                }
            }
        }
    }

    this->AfterMove(changed);
}

/**
 * 按键事件的响应方法。
 * @param key  按键
 * @return 事件在此被处理则返回 TRUE，否则返回 FALSE
 */
BOOL Game::OnKeyPressed(UINT key) {
    if (mStatus != PLAYING) {
        return FALSE;
    }

    switch (key) {
        case VK_DOWN:
            this->MoveDown();
            return TRUE;
        case VK_LEFT:
            this->MoveLeft();
            return TRUE;
        case VK_RIGHT:
            this->MoveRight();
            return TRUE;
        case VK_UP:
            this->MoveUp();
            return TRUE;
    }

    return FALSE;
}

/**
 * 游戏开始。
 */
void Game::Start() {
    // 重绘游戏区域
    this->DrawGameBoard();

    // 数字矩阵清零
    ZeroMemory(mNumbers, sizeof(mNumbers));

    // 创建随机数字
    this->CreateRandomNumber();

    // 将状态设置为游戏中
    mStatus = PLAYING;
}
